﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Gallio.Model;
using Gallio.Reflection;
using NFit.Core;
using NFit.Core.DataSources;

namespace NFit.GallioAdapter
{
    class NFitExplorerState
    {
        private const string NFitFrameworkFormat = "NFit framework {0}";
        public const string NFitFrameworkName = "NFit framework";
        private const string NFitAssemblyDisplay = "NFit.TestFramework";

        private readonly TestModel testModel;
        private readonly Dictionary<Version, ITest> frameworkTests;
        private readonly Dictionary<IAssemblyInfo, ITest> assemblyTests;

        public NFitExplorerState(TestModel testModel)
        {
            this.testModel = testModel;
            frameworkTests = new Dictionary<Version, ITest>();
            assemblyTests = new Dictionary<IAssemblyInfo, ITest>();
        }

        public void ExploreAssembly(IAssemblyInfo assembly, Action<ITest> consumer)
        {
            SimpleLog.Write("Exploring assembly " + assembly.FullName);
            Version frameworkVersion = GetFrameworkVersion(assembly);

            if (frameworkVersion != null)
            {
                SimpleLog.Write("NFit found " + frameworkVersion);
                ITest frameworkTest = GetFrameworkTest(frameworkVersion, testModel.RootTest);
                ITest assemblyTest = GetAssemblyTest(assembly, frameworkTest);

                if (assemblyTest != null && consumer != null)
                {
                    consumer(assemblyTest);
                    SimpleLog.Write("assembly Tests found " + assemblyTest.Children.Count);
                }
                else
                {
                    SimpleLog.Write("assembly Tests found " + (assemblyTest != null).ToString());
                    SimpleLog.Write("consumer " + (consumer != null).ToString());
                }
            }
            else
            {
                SimpleLog.Write("NFit not found " + frameworkVersion);
            }
        }

        private static Version GetFrameworkVersion(IAssemblyInfo assembly)
        {
            AssemblyName frameworkAssemblyName = ReflectionUtils.FindAssemblyReference(assembly, NFitAssemblyDisplay);
            return frameworkAssemblyName != null ? frameworkAssemblyName.Version : null;
        }

        private ITest GetFrameworkTest(Version frameworkVersion, ITest rootTest)
        {
            ITest frameworkTest;
            if (!frameworkTests.TryGetValue(frameworkVersion, out frameworkTest))
            {
                frameworkTest = CreateFrameworkTest(frameworkVersion);
                rootTest.AddChild(frameworkTest);

                frameworkTests.Add(frameworkVersion, frameworkTest);
            }

            return frameworkTest;
        }

        private static ITest CreateFrameworkTest(Version frameworkVersion)
        {
            BaseTest frameworkTest = new BaseTest(String.Format(NFitFrameworkFormat, frameworkVersion), null);
            frameworkTest.LocalIdHint = "NFit";
            frameworkTest.Kind = TestKinds.Framework;

            return frameworkTest;
        }

        private ITest GetAssemblyTest(IAssemblyInfo assembly, ITest frameworkTest)
        {
            ITest assemblyTest;
            if (!assemblyTests.TryGetValue(assembly, out assemblyTest))
            {
                assemblyTest = CreateAssemblyTest(assembly);
                if (assemblyTest != null)
                {
                    frameworkTest.AddChild(assemblyTest);

                    assemblyTests.Add(assembly, assemblyTest);
                }
            }

            return assemblyTest;
        }

        private ITest CreateAssemblyTest(IAssemblyInfo assembly)
        {
            // Resolve test assembly.
            string location;
            try
            {
                location = assembly.Resolve(true).Location;
                SimpleLog.Write("location : " + location);
            }
            catch (Exception ex)
            {
                testModel.AddAnnotation(new Annotation(AnnotationType.Error, assembly, "Could not resolve the location of an NFit test assembly.", ex));
                return null;
            }

            try
            {
                /*
                var datasources = GetDatasources(assembly);

                if (datasources != null)
                {
                    foreach(var source in datasources)
                    {
                        CreateTestsFromDatasource(assembly, source, consumer);
                    }
                }
                 */
                AssemblyDataSource source = new AssemblyDataSource();
                source.SpecAssembly = assembly.Resolve(false);
                IEnumerable<SpecificationDefinition> specs = source.GetTestDefinitions();
                NFitDataSourceTest assemblyTest = null;
                int specsCount = 0;
                if (specs != null && (specsCount=specs.Count()) > 0)
                {
                    SimpleLog.Write("nb specs found : " + specsCount);
                    assemblyTest = new NFitDataSourceTest(assembly, source);
                    PopulateMetadata(assemblyTest);

                    foreach (SpecificationDefinition spec in specs)
                        BuildTest(assemblyTest, spec);
                    SimpleLog.Write("return assembly test with : " + assemblyTest.Children.Count);
                }
                return assemblyTest;
            }
            catch (Exception ex)
            {
                testModel.AddAnnotation(new Annotation(AnnotationType.Error, assembly, "An exception was thrown while exploring an NFit test assembly.", ex));
                return null;
            }
        }



        private static void BuildTest(NFitTest parentTest, SpecificationDefinition spec)
        {
            ICodeElementInfo codeElement = null;
            SimpleLog.Write("Creating test " + spec.Name);
            // Build the test.
            NFitTest test = new NFitTest(spec.Name, codeElement, spec);
            test.Kind = TestKinds.Test;
            test.IsTestCase = true;

            PopulateMetadata(test);

            parentTest.AddChild(test);
            BuildTestChildren(test, spec);
        }

        private static void BuildTestChildren(NFitTest parentTest, SpecificationDefinition parent)
        {
            if (parent.ChildSpecs != null)
            {
                foreach (SpecificationDefinition child in parent.ChildSpecs)
                    BuildTest(parentTest, child);
            }
        }

        private static void PopulateMetadata(NFitTest test)
        {
            SpecificationDefinition nunitTest = test.Spec;

            /*if (!String.IsNullOrEmpty(nunitTest.Description))
                test.Metadata.Add(MetadataKeys.Description, nunitTest.Description);

            if (!String.IsNullOrEmpty(nunitTest.IgnoreReason))
                test.Metadata.Add(MetadataKeys.IgnoreReason, nunitTest.IgnoreReason);

            foreach (string category in nunitTest.Categories)
                test.Metadata.Add(MetadataKeys.Category, category);

            foreach (DictionaryEntry entry in nunitTest.Properties)
                test.Metadata.Add(entry.Key.ToString(), entry.Value != null ? entry.Value.ToString() : null);
            */

            ICodeElementInfo codeElement = test.CodeElement;
            if (codeElement != null)
            {
                // Add documentation.
                string xmlDocumentation = codeElement.GetXmlDocumentation();
                if (xmlDocumentation != null)
                    test.Metadata.Add(MetadataKeys.XmlDocumentation, xmlDocumentation);

                // Add assembly-level metadata.
                IAssemblyInfo assemblyInfo = codeElement as IAssemblyInfo;
                if (assemblyInfo != null)
                    ModelUtils.PopulateMetadataFromAssembly(assemblyInfo, test.Metadata);
            }
        }
    }
}
